#!/usr/bin/env python
# SPDX-FileCopyrightText: Copyright (c) 2025, Kr8s Developers (See LICENSE for list)
# SPDX-License-Identifier: BSD 3-Clause License
# /// script
# requires-python = ">=3.12"
# dependencies = [
#     "ruamel-yaml",
# ]
# ///
import json
import os
import re
import urllib.request
from datetime import datetime
from pathlib import Path

from ruamel.yaml import YAML

yaml = YAML(typ="rt")
yaml.width = 2**16
yaml.preserve_quotes = True
yaml.indent(mapping=2, sequence=4, offset=2)

DATE_FORMAT = "%Y-%m-%d"
SUPPORT_MODE = "extended"  # "standard" or "extended"


def extract_dates(data):
    if "extendedSupport" in data and isinstance(data["extendedSupport"], str):
        yield datetime.strptime(data["extendedSupport"], DATE_FORMAT)
    if "eol" in data and isinstance(data["eol"], str):
        yield datetime.strptime(data["eol"], DATE_FORMAT)
    if "lts" in data and isinstance(data["lts"], str):
        yield datetime.strptime(data["lts"], DATE_FORMAT)


def has_eol(data):
    """Check if a version has a defined end of life."""
    if "eol" in data and isinstance(data["eol"], str):
        return True
    if "extendedSupport" in data and isinstance(data["extendedSupport"], str):
        return True
    if "lts" in data and isinstance(data["lts"], str):
        return True
    return False


def get_support_date(data):
    dates = list(extract_dates(data))
    if not dates:
        return None
    if SUPPORT_MODE == "standard":
        return min(dates)
    elif SUPPORT_MODE == "extended":
        return max(dates)


def get_kubernetes_oss_versions():
    print(
        "Loading Kubernetes versions from https://endoflife.date/api/kubernetes.json..."
    )
    with urllib.request.urlopen("https://endoflife.date/api/kubernetes.json") as url:
        data = json.load(url)
        data = [
            {
                "cycle": x["cycle"],
                "latest_version": x["latest"],
                "eol": get_support_date(x),
            }
            for x in data
            if has_eol(x)
        ]
        data.sort(key=lambda x: x["eol"], reverse=True)
    return data


def get_azure_aks_versions():
    url = "https://endoflife.date/api/azure-kubernetes-service.json"
    print(f"Loading Azure AKS versions from {url}...")
    with urllib.request.urlopen(url) as payload:
        data = json.load(payload)

        data = [
            {
                "cycle": x["cycle"],
                "eol": get_support_date(x),
            }
            for x in data
            if has_eol(x)
        ]
        data.sort(key=lambda x: x["eol"], reverse=True)
    return data


def get_amazon_eks_versions():
    url = "https://endoflife.date/api/amazon-eks.json"
    print(f"Loading Amazon EKS versions from {url}...")
    with urllib.request.urlopen(url) as payload:
        data = json.load(payload)
        data = [
            {
                "cycle": x["cycle"],
                "eol": get_support_date(x),
            }
            for x in data
            if has_eol(x)
        ]
        data.sort(key=lambda x: x["eol"], reverse=True)
    return data


def get_google_kubernetes_engine_versions():
    url = "https://endoflife.date/api/google-kubernetes-engine.json"
    print(f"Loading Google Kubernetes Engine versions from {url}...")
    with urllib.request.urlopen(url) as payload:
        data = json.load(payload)
        data = [
            {
                "cycle": x["cycle"],
                "eol": get_support_date(x),
            }
            for x in data
            if has_eol(x)
        ]
        data.sort(key=lambda x: x["eol"], reverse=True)
    return data


def extend_versions(versions, extended_versions, provider):
    print(f"Extending EOL dates with {provider} support dates...")
    for extended_version in extended_versions:
        for version in versions:
            if version["cycle"] == extended_version["cycle"]:
                if version["eol"] < extended_version["eol"]:
                    print(
                        f"Extending EOL date for {version['cycle']} from {version['eol']:%Y-%m-%d} to "
                        f"{provider} support date {extended_version['eol']:%Y-%m-%d}"
                    )
                    version["eol"] = extended_version["eol"]
    return versions


def dockerhub_auth():
    if not os.environ.get("DOCKERHUB_USERNAME") or not os.environ.get(
        "DOCKERHUB_TOKEN"
    ):
        return None
    data = json.dumps(
        {
            "identifier": os.environ.get("DOCKERHUB_USERNAME"),
            "secret": os.environ.get("DOCKERHUB_TOKEN"),
        }
    ).encode()
    req = urllib.request.Request(
        "https://hub.docker.com/v2/auth/token",
        data=data,
        headers={"Content-Type": "application/json"},
    )
    with urllib.request.urlopen(req) as resp:
        return json.load(resp)["access_token"]


def get_kind_versions():
    print("Loading Kubernetes tags from https://hub.docker.com/r/kindest/node/tags...")
    container_tags = []
    headers = {}
    jwt_token = dockerhub_auth()
    if jwt_token:
        headers = {"Authorization": f"Bearer {jwt_token}"}
    next_url = "https://hub.docker.com/v2/repositories/kindest/node/tags"
    while next_url:
        req = urllib.request.Request(next_url, headers=headers)
        with urllib.request.urlopen(req) as url:
            results = json.load(url)
            container_tags += results["results"]
            if "next" in results and results["next"]:
                next_url = results["next"]
            else:
                next_url = None
    return container_tags


def get_versions():
    versions = get_kubernetes_oss_versions()
    versions = extend_versions(versions, get_azure_aks_versions(), "Azure AKS")
    versions = extend_versions(versions, get_amazon_eks_versions(), "Amazon EKS")
    versions = extend_versions(
        versions, get_google_kubernetes_engine_versions(), "Google Kubernetes Engine"
    )
    print("Pruning versions that are past their support date...")
    versions = [x for x in versions if x["eol"] > datetime.now()]

    container_tags = get_kind_versions()

    for version in versions:
        try:
            version["latest_kind_container"] = [
                x["name"]
                for x in container_tags
                if version["cycle"] in x["name"] and "alpha" not in x["name"]
            ][0][1:]
        except IndexError:
            version["latest_kind_container"] = None

    before_length = len(versions)
    print("Pruning versions that do not have a kind release yet...")
    versions[:] = [x for x in versions if x["latest_kind_container"] is not None]
    print(f"Pruned {before_length - len(versions)} versions")
    return versions


def update_workflow(versions, workflow_path):
    workflow_path = Path(workflow_path)
    workflow = yaml.load(workflow_path)
    latest_kind_container = versions[0]["latest_kind_container"]
    if "minimal-deps" in workflow["jobs"]:
        workflow["jobs"]["minimal-deps"]["strategy"]["matrix"]["kubernetes-version"][
            0
        ] = latest_kind_container
    workflow["jobs"]["test"]["strategy"]["matrix"]["kubernetes-version"][
        0
    ] = latest_kind_container
    workflow["jobs"]["test"]["strategy"]["matrix"]["include"] = []
    for version in versions[1:]:
        workflow["jobs"]["test"]["strategy"]["matrix"]["include"].append(
            {
                "python-version": workflow["jobs"]["test"]["strategy"]["matrix"][
                    "python-version"
                ][-1],
                "kubernetes-version": version["latest_kind_container"],
            }
        )
    yaml.dump(workflow, workflow_path)


def update_badges(filename, versions):
    readme = Path(filename).read_text()
    # Use regex to replace the badge
    v = [x["cycle"] for x in versions]
    v.sort()
    version_list = "%7C".join(v)
    readme = re.sub(
        r"img.shields.io/badge/Kubernetes%20support.*-blue",
        f"img.shields.io/badge/Kubernetes%20support-{version_list}-blue",
        readme,
    )
    Path(filename).write_text(readme)


def update_version_support(versions):
    version_support = Path("kr8s/_constants.py").read_text()
    version_support = re.sub(
        r"KUBERNETES_MINIMUM_SUPPORTED_VERSION = .*",
        f"KUBERNETES_MINIMUM_SUPPORTED_VERSION = parse_version(\"{versions[-1]['cycle']}\")",
        version_support,
    )
    version_support = re.sub(
        r"KUBERNETES_MAXIMUM_SUPPORTED_VERSION = .*",
        f"KUBERNETES_MAXIMUM_SUPPORTED_VERSION = parse_version(\"{versions[0]['cycle']}\")",
        version_support,
    )
    Path("kr8s/_constants.py").write_text(version_support)


def main():
    versions = get_versions()
    print(f"Latest version: {versions[0]['cycle']}")
    print("Supported versions:")
    for version in versions:
        print(
            f"For {version['cycle']} using kindest/node {version['latest_kind_container']}"
            f" until {version['eol']:%Y-%m-%d}"
        )

    if not os.environ.get("DEBUG"):
        update_workflow(versions, ".github/workflows/test-kr8s.yaml")
        update_workflow(versions, ".github/workflows/test-kubectl-ng.yaml")
        update_badges("README.md", versions)
        update_badges("docs/index.md", versions)
        update_version_support(versions)
    else:
        print("DEBUG env var set, skipping file updates")


if __name__ == "__main__":
    main()
